package org.bootlimn.renderer;
import java.awt.Point;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;


import org.bootlimn.common.Common;
import org.bootlimn.common.Process;
import org.bootlimn.common.Stats;

/**
 * PNGRenderer renders the boot chart as a PNG image.
 */
public class SVGRenderer extends Renderer {
	//private static final Logger log = Logger.getLogger(SVGRenderer.class.getName());
	
	/** Chart SVG template file. */
	private static final File CHART_SVG_TEMPLATE = new File("svg/bootlimn.svg.template");
	/** Process SVG template file. */
	private static final File PROCESS_SVG_TEMPLATE = new File("svg/process.svg.template");
	
	/** SVG style sheet. */
	private static final File SVG_CSS_FILE = new File("svg/style.css");
	
	/** Whether to compress SVG output. */
	private static final boolean COMPRESS_SVG = true;
	
	private static final DateFormat BOOT_TIME_FORMAT =
		new SimpleDateFormat("m:ss", Common.LOCALE);
	
	private static final int MIN_IMG_W = 800;
	
	
	/**
	 * Whether to inline the CSS file.  Some SVG renderers (e.g. ksvg) do not
	 * support CSS so the style properties are parsed from the CSS file and
	 * inlined in the SVG document.
	 */
	//private static final boolean INLINE_CSS = true;
	
	
	/**
	 * Render the chart.
	 * @param level 
	 * @param uniqueP 
	 * 
	 * @param headers       header properties to include in the title banner
	 * @param bootStats     boot statistics
	 * @param os            the output stream to write t
	 * @throws IOException  if an I/O error occurs
	 */
	public void render(List<Process> processes, List<Stats> stats, List<Process> uniqueP, int[] level)
		throws IOException {
		
		
		// Load the templates
		String chartTemplate = "";
		
		chartTemplate = Common.loadFile(CHART_SVG_TEMPLATE);
		List<String> myProcesses;
		myProcesses = new ArrayList<String>();
		
		//System.out.println(stats.size());
		
		// Get the list of processes
		int nP = processes.size();
		int processCount = 0;
		
		boolean pFlag = true;
		
		for (int k=0; k<nP; k++) {
			pFlag = true;
			Process item = processes.get(k);
			String itemName = item.getExecname();

			for (int j=0; j<myProcesses.size(); j++) {
					if (myProcesses.get(j).equals(itemName)) {
					pFlag = false;
					break;
				}
			}
			
			if (pFlag == true) {
				myProcesses.add(itemName);
			}
				
		}
		
		processCount = myProcesses.size();
		
		/**
		 * Load the bootchart SVG template.  Its arguments are:
		 *  0: Dimension
		 *  1: Style sheet
		 *  2: Title
		 *  3: System information
		 *  4: OS information
		 *  5: CPU information
		 *  6: Kernel cmdline
		 *  7: Boot time
		 *  8: CPU Timeline
		 *  9: CPU Utilization
		 * 10: CPU Throughput
		 * 11: Disk Timeline
		 * 12: Disk Utilization
		 * 13: Disk Throughput
		 * 14: Process axis labels
		 * 15: Process Timeline
		 * 16: Process chart (formatted using the process template)
		 */
		
		
		/*
		 * {0} Size of the image
		 */
		int nS = stats.size();
		int dur = nS;
		//int secW = 25;
		int w = dur + 20;
		
		//System.out.println(processCount);
		
		int procH = 16;
		int headerH = 300;
		int h = procH * processCount + headerH;
		String dimension = "width=\"" + Math.max(w, MIN_IMG_W) + "px\" height=\"" + (h + 30) + "px\"";
		
		
		/*
		 * {1} Stylesheet
		 */
		String style = "";
		style = Common.loadFile(SVG_CSS_FILE);		
		
		
		/*
		 * {2}, {3}, {4}, {5}, {6}, {7} -- Get the Meta
		 */
		String title = "Bootlimn";
		String uname = "";
		String release = "";
		String cpu = "";
		String kopt = "";
		Date dDur = new Date(Math.round(dur / 1000.0) * 1000);
		String bootTime = "time: " + BOOT_TIME_FORMAT.format(dDur);
		
		
		/*
		 * {8}, {9}. {10} Render CPU/IO chart
		 */
		
		// time ticks
		int barH = 50;
		StringBuffer ticksBuff = new StringBuffer();
		for (int i=0; i<=dur; i = i + 20) {
			int x = i;
			String tclass = (i % 100 == 0) ? "class=\"Bold\" " : "";
			ticksBuff.append("<line " + tclass + "x1=\"" + x + "\" "
				+ "y1=\"0\" x2=\"" + x + "\" y2=\"" + barH + "\"/>\n");
		}
		String cpuTicks = ticksBuff.toString();
		cpuTicks = cpuTicks.replaceAll("\n", "\n\t\t\t");
		cpuTicks = cpuTicks.trim();
		ticksBuff = null;
		
		StringBuffer cpuUtil = new StringBuffer();
		StringBuffer cpuTput = new StringBuffer();
		if (stats != null) {
			/*
			 * Get the CPU Utilization
			 */
			int lastX = 0;
			for (Iterator<Stats> i = stats.iterator(); i.hasNext();) {
				Stats sItem = (Stats)i.next();
				int posX = lastX + 1;
				int posY = 50 - sItem.getCPUutil() / 2;
				if (cpuUtil.length() == 0) {
					cpuUtil.append(posX + "," + barH);
				}
				cpuUtil.append(" " + posX + "," + posY);
				lastX = posX;
			}
			cpuUtil.append(" " + lastX + "," + barH);
			
			/*
			 * Get the CPU Throughput
			 */
			lastX = 0;
			for (Iterator<Stats> i = stats.iterator(); i.hasNext();) {
				Stats sItem = (Stats)i.next();
				int posX = lastX + 1;
				int posY = 50 - sItem.getCPUtput() / 2;
				if (posY < 0)
					posY = 0;
				//System.out.println(posY);
				if (cpuTput.length() == 0) {
					cpuTput.append(posX + "," + barH);
				}
				cpuTput.append(" " + posX + "," + posY);
				lastX = posX;
			}
			cpuTput.append(" " + lastX + "," + barH);
		}


		/*
		 * {11}, {12}. {13} Render Disk chart
		 */
		StringBuffer diskUtil = new StringBuffer();
		StringBuffer diskTput = new StringBuffer();
		
		if (stats != null) {
			// Disk utilization
			int lastX = 0;
			for (Iterator<Stats> i = stats.iterator(); i.hasNext();) {
				Stats sItem = (Stats)i.next();
				int posX = lastX + 1;
				int posY = 50 - sItem.getDiskutil() / 2;
				if (diskUtil.length() == 0) {
					diskUtil.append(posX + "," + barH);
				}
				diskUtil.append(" " + posX + "," + posY);
				lastX = posX;
			}
			diskUtil.append(" " + lastX + "," + barH);
			
			// Disk Throughput
			lastX = 0;
			for (Iterator<Stats> i = stats.iterator(); i.hasNext();) {
				Stats sItem = (Stats)i.next();
				int posX = lastX + 1;
				int posY = 50 - sItem.getDisktput() / 2;
				if (posY < 0)
					posY = 0;
				if (diskTput.length() == 0) {
					diskTput.append(posX + "," + barH);
				}
				diskTput.append(" " + posX + "," + posY);
				lastX = posX;
			}
			diskTput.append(" " + lastX + "," + barH);
		}
		
		/*
		 * Render the process chart
		 */
 		// time ticks
		int treeH = processCount * procH;
		ticksBuff = new StringBuffer();
		StringBuffer axisBuff = new StringBuffer();
		for (int i=0; i<=dur; i = i+20) {
			int x = i;
			String tclass = (i % 100 == 0) ? "class=\"Bold\" " : "";
			ticksBuff.append("<line " + tclass + "x1=\"" + x + "\" "
				+ "y1=\"0\" x2=\"" + x + "\" y2=\"" + treeH + "\"/>\n");
			if (i > 0 && i % 100 == 0) {
				String label = (i/10) + "s";
				int len = label.length();
				axisBuff.append("<text class=\"AxisLabel\" x=\""
					+ x + "\" y=\"0\" dx=\"" + (-len/4.0) + "em\" dy=\"-3\">"
					+ label + "</text>\n");
			}
		}
		String procTicks = ticksBuff.toString();
		String axisLabels = axisBuff.toString();
		procTicks = procTicks.replaceAll("\n", "\n\t\t\t");
		axisLabels = axisLabels.replaceAll("\n", "\n\t\t\t");
		procTicks = procTicks.trim();
		axisLabels = axisLabels.trim();
		
		String treeDimensions = "width=\"100%\" height=\""+treeH+"\"";
		
		String processChart = renderProcessChart(myProcesses, processes, Math.max(w, MIN_IMG_W), treeH, uniqueP, level);

				
		/*
		 * Write the info to our SVG file
		 */
		Object[] chartArgs = {
				dimension, style, title, uname, release, cpu, kopt, bootTime,
				cpuTicks, cpuUtil.toString(), cpuTput.toString(), 
				cpuTicks, diskUtil.toString(), diskTput.toString(),
				axisLabels, treeDimensions, procTicks, processChart
			};
		
		String svgContent = MessageFormat.format(chartTemplate, chartArgs);
		
		//System.out.println(svgContent);
		//System.out.println(chartTemplate);
		//System.out.println(processTemplate);
		//System.out.println(style);

		try {
	        	BufferedWriter out = new BufferedWriter(new FileWriter("bootlimn.svg"));
	        	out.write(svgContent);
	        	out.close();
	    	} catch (IOException e) {
	    		System.out.println("Could not create file bootlimn.svg");
	   	 }

				
	}
	
	static String processTemplate = null;

	private String renderProcessChart(List<String> myProcesses, List<Process> processes, int width, int height, List<Process> uniqueP, int[] level) {
		
		List<String> processChart = new ArrayList<String>();
				
		StringBuffer processC = new StringBuffer();
		
		if (processTemplate == null) {
			try {
				processTemplate = Common.loadFile(PROCESS_SVG_TEMPLATE);
			} catch (FileNotFoundException e) {
				throw new RuntimeException(e);
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
		
		int nP = processes.size();	
		
		//System.out.println(myProcesses);
		
		int scale = 1000;
		
		int start = processes.get(0).getTime();
		System.out.println("start is " + start);
		
		Process firstInstance = null;
				
		for (int i=0; i<myProcesses.size(); i++) {
			String node = (String)myProcesses.get(i);
			
			int x = 0, y = 0, w = 0;
			
			boolean kFlag = false;
			
			int stime = 0;
			int etime = 0;
			for (int k=0; k<nP; k++) {
				Process item = processes.get(k);
				String itemName = item.getExecname();

				if (itemName.equals(node) && !kFlag) {
					stime = item.getTime();
				//	System.out.println("stime is "+ stime);
					firstInstance = item;
					kFlag = true;
				}
				if (itemName.equals(node)) {
					//if (item.getProbefunc().equals("sys_exit"))
						etime = item.getTime();
				}
				
			}	
			//System.out.println("etime is " + etime);	
			x = (stime - start) / scale;
			//System.out.println("x is " + x);
			y = i * 16;
			w = ((etime - start) - (stime - start)) / scale;
			if (etime == 0)
				w = width - x;
			//System.out.println("w is " + w);
			
			String position = x + "," + y;
			String border = "width=\"" + w + "\" height=\"" + 16 + "\"";
			
			String labelText = node;
			String titleText = node;
			
			
			StringBuffer tbuff = new StringBuffer();
			tbuff.append("<rect class=\"Sleeping\" x=\""+ 0 +"\" y=\"0\" width=\"" + w
				+ "\" height=\"" + 16 + "\"/>\n");
			
			
			for (int k=0; k<nP; k++) {
				Process item = processes.get(k);
				String itemName = item.getExecname();
								
				int tx = 0;
				int tw = 1;
				
				String tclass = "Sleeping";
				String opacity = "";

				if (itemName.equals(node)) {
					String probe = item.getProbefunc();
					
					int time =(item.getTime() - start)/scale;
					tx = ((item.getTime() - start)/scale) - x;
					//System.out.println(X);
					//System.out.println(item.getTime());
					if (probe.equals("sys_clone")) {
						tclass = "Clone";
						
						for (int t=k+1; t<nP; t++) {
							Process it = processes.get(t);
							String iName = it.getExecname();
							if (iName.equals(node)) {
								tw = ((it.getTime() - start)/scale) - time;
								break;
							}
						}
						
					} else if (probe.equals("sys_execve")) {
						tclass = "Exec";
						
						for (int t=k+1; t<nP; t++) {
							Process it = processes.get(t);
							String iName = it.getExecname();
							if (iName.equals(node)) {
								tw = ((it.getTime() - start)/scale) - time;
								break;
							}
						}
						
					} else if (probe.equals("sys_wait4")) {
						tclass = "Wait";
						
						for (int t=k+1; t<nP; t++) {
							Process it = processes.get(t);
							String iName = it.getExecname();
							if (iName.equals(node)) {
								tw = ((it.getTime() - start)/scale) - time;
								break;
							}
						}
						
					} else if (probe.equals("sys_pause")) {
						tclass = "Pause";
						
						for (int t=k+1; t<nP; t++) {
							Process it = processes.get(t);
							String iName = it.getExecname();
							if (iName.equals(node)) {
								tw = ((it.getTime() - start)/scale) - time;
								break;
							}
						}
						
					} else if (probe.equals("sys_fork")) {
						tclass = "Fork";
						
						for (int t=k+1; t<nP; t++) {
							Process it = processes.get(t);
							String iName = it.getExecname();
							if (iName.equals(node)) {
								tw = ((it.getTime() - start)/scale) - time;
								break;
							}
						}
						
					} else if (probe.equals("sys_exit")) {
						tclass = "Exit";
					} 
					if (tw < 1)
						tw = 1;
					//System.out.println(item.getTime());
					if (tclass != null && !(tx < 0)) {
						tbuff.append("<rect class=\"" + tclass + "\" "
							+ opacity + "x=\"" + tx + "\" y=\"0\" width=\"" + tw
							+ "\" height=\"" + 16 + "\"/>\n");
					}
				}	
				
				
			}
			
			
			String timeline = tbuff.toString();
			timeline = timeline.replaceAll("\n", "\n\t\t");
			timeline = timeline.trim();
			
			String labelPos = "";
			/*if (w < 200 && x + w + 200 < x + width) {
				labelPos = "dx=\"2\" dy=\""+ (16 - 1) + "\" x=\"" + w + "\" y=\"0\"";
			} else {
				labelPos = "dx=\"2\" dy=\""+ (16 - 1) + "\" x=\"0\" y=\"0\"";
			}*/
			labelPos = "dx=\"2\" dy=\""+ (16 - 1) + "\" x=\"0\" y=\"0\"";
			
			String pid = firstInstance.getPexecname();
			int px = -1, py = -1;
			
			if (!firstInstance.getExecname().equals(firstInstance.getPexecname())) {
				for (int k=0; k<nP; k++) {
					Process item = processes.get(k);
					if (item.getExecname().equals(pid)) {
						//System.out.println(item.getTime()+" ");
						px = (item.getTime() - start) / scale;
						for (int c=0; c<myProcesses.size(); c++) {
							if (firstInstance.getPexecname().equals(myProcesses.get(c).toString())) {
								py = (c + 1)* 16;
								//System.out.println("Process = <" + myProcesses.get(i) + "> | Parent Process = <" + myProcesses.get(c) + "> " + px+" "+py);
								break;
							}
						}
						break;
					}
				}
			}
			
			//System.out.println(myProcesses.get(i) + " " + px+" "+py);
			//System.out.println(node + "\n position --> "+position+"\n border --> " + border+ "\n labelPos --> " + labelPos+"\n timeline --> " + timeline);
			String tree = renderTree( x,y,px,py);
			
			Object[] procArgs =
			{position, timeline, border, labelPos, labelText, titleText, tree};
			String procContent = MessageFormat.format(processTemplate, procArgs);
		
			processChart.add(procContent);
			processC.append(procContent);
			
		}
		
		String c = processC.toString();
		c = c.replaceAll("\n", "\n\t\t");
		c = c.trim();
		//System.out.println(c);
		
		return c;
	}

	
	
	private String renderTree(int x, int y, int px, int py) {
		StringBuffer connector = new StringBuffer();		
		int procH = 16;
		//tree.append("<line fill=\"none\" stroke=\"#000000\" stroke-dasharray=\"3,3\" x1=\"0\" y1=\"8\" x2=\"-905\" y2=\"8\"/>");
		int k = px;
		
		if (px != -1 && py != -1) {
			px -= x;
			py -= y;
			if (k != 0) {
				int depOffX = 3;
				int depOffY = procH / 4;
				Point p1 = new Point(0, procH/2);
				Point p2 = new Point(px - depOffX, procH/2);
				connector.append(getConnLineSVG(p1, p2) + "\n");
				p1 = new Point(px - depOffX, procH/2);
				p2 = new Point(px - depOffX, py - depOffY);
				connector.append(getConnLineSVG(p1, p2) + "\n");
				p1 = new Point(px - depOffX, py - depOffY);
				p2 = new Point(px, py - depOffY);
				connector.append(getConnLineSVG(p1, p2) + "\n");
			} else {
				Point p1 = new Point(0, procH/2);
				Point p2 = new Point(px, procH/2);
				connector.append(getConnLineSVG(p1, p2) + "\n");
				p1 = new Point(px, procH/2);
				p2 = new Point(px, py);
				connector.append(getConnLineSVG(p1, p2) + "\n");
			}
		}
		
		String c = connector.toString();
		c = c.replaceAll("\n", "\n\t\t");
		c = c.trim();
		//System.out.println(c);
		//System.out.println(uniqueP.size());
		
		
		return c;
	}

	private static String getConnLineSVG(Point p1, Point p2) {
		String style = "stroke=\"#000000\" style=\"stroke-dasharray: 3,3;\" "; // librsvg workaround
		String svg = "<line " + style + "x1=\"" + p1.x + "\" y1=\"" + p1.y + "\" "
			+ "x2=\"" + p2.x + "\" y2=\"" + p2.y + "\"/>";
		return svg;
	}

	/*
	 * inherit javadoc
	 */
	public String getFileSuffix() {
		return COMPRESS_SVG ? "svgz" : "svg";
	}
	
}
